/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.commons.eventbus;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.squareup.otto.DeadEvent;

/**
 * 
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 *
 */
abstract class PriorityHub {
    
    private final Class<?> priorityClass;
    private final Map<String, PriorityList<?>> prioritysMap;
    private final Map<Object, Set<AnnotationPriority>> annotationsMap;
    
    protected PriorityHub(Class<?> priorityClass) {
        this.priorityClass = priorityClass;
        prioritysMap = new ConcurrentHashMap<>();
        annotationsMap = new ConcurrentHashMap<>();
    }
    
    /**
     * 
     * @param object
     */
    public void register(Object object) {
        verifyNotNull(object, "object");
        if (annotationsMap.containsKey(object)) {
            throw new IllegalArgumentException("This object is be registered.");
        }
        
        if (priorityClass.isInstance(object)) {
            IPriority<?> priority = (IPriority<?>) object;
            String address = getAddress(priority, priorityClass);
            register(address, priority);
        } else {
            Set<AnnotationPriority> prioritys = getAnnotationPrioritys(object);
            if (!prioritys.isEmpty()) {
                annotationsMap.put(object, prioritys);
                for (AnnotationPriority priority : prioritys) {
                    register(priority.getAddress(), priority);
                }
            }
        }
    }
    
    /**
     * 
     * @param address
     * @param priority
     */
    protected <E> void register(String address, IPriority<E> priority) {
        PriorityList<E> prioritys = getPriorityList(address);
        if (prioritys == null) {
            prioritys = new PriorityList<E>();
            prioritysMap.put(address, prioritys);
        } else if (prioritys.contains(priority)) {
            throw new IllegalArgumentException("Be registered.");
        }
        prioritys.add(priority);
    }
    
    /**
     * 
     * @param object
     */
    public void unregister(final Object object) {
        verifyNotNull(object, "object");
        if (object instanceof IPriority) {
            IPriority<?> priority = (IPriority<?>) object;
            String address = getAddress(priority, priorityClass);
            unregister(address, priority);
        } else {
            Set<AnnotationPriority> prioritys = annotationsMap.get(object);
            if (prioritys != null) {
                for (AnnotationPriority priority : prioritys) {
                    unregister(priority.getAddress(), priority);
                }
                annotationsMap.remove(object);
            }
        }
    }
    
    /**
     * 
     * @param address
     * @param priority
     */
    protected <E> void unregister(String address, IPriority<E> priority) {
        PriorityList<E> prioritys = getPriorityList(address);
        if (priority == null) {
            throw new IllegalArgumentException("Not registered.");
        }
        prioritys.remove(priority);
        if (prioritys.isEmpty()) {
            prioritysMap.remove(address);
        }
    }
    
    /**
     * 
     * @param event
     */
    public <E> void post(E event) {
        verifyNotNull(event, "event");
        post(event.getClass().getName(), event);
    }
    
    /**
     * 
     * @param address
     * @param event
     */
    public <E> void post(String address, E event) {
        verifyNotNull(address, "address");
        verifyNotNull(event, "event");
        PriorityList<E> prioritys = getPriorityList(address);
        if (prioritys != null && !prioritys.isEmpty()) {
            doPost(address, event, prioritys);
        } else if (!(event instanceof DeadEvent)) {
            post(new DeadEvent(this, event));
        }
    }
    
    /**
     * 
     * @param address
     * @return
     */
    @SuppressWarnings("unchecked")
    protected <E> PriorityList<E> getPriorityList(String address) {
        return (PriorityList<E>) prioritysMap.get(address);
    }
    
    protected abstract Set<AnnotationPriority> getAnnotationPrioritys(Object object);
    
    protected abstract <E> void doPost(String address, E event, PriorityList<E> prioritys);
    
    /**
     * 
     * @param priority
     * @param classType
     * @return
     */
    static String getAddress(IPriority<?> priority, Class<?> classType) {
        Class<?> clazz = priority.getClass();
        while (true) {
            try {
                java.lang.reflect.Type[] types = clazz.getGenericInterfaces();
                for (Type type : types) {
                    if (type instanceof ParameterizedType) {
                        ParameterizedType ptype = (ParameterizedType) type;
                        if (classType.equals(ptype.getRawType())) {
                            Type cmpType = ptype.getActualTypeArguments()[0];
                            Class<?> cmpClazz;
                            if (cmpType instanceof ParameterizedType) {
                                cmpClazz = (Class<?>) ((ParameterizedType) cmpType).getRawType();
                            } else {
                                cmpClazz = (Class<?>) ptype.getActualTypeArguments()[0];
                            }
                            return cmpClazz.getName();
                        }
                    }
                }
            } catch (Exception ex) {
            }
            clazz = clazz.getSuperclass();
            if (clazz == null) {
                break;
            }
        }
        return Object.class.getName();
    }
    
    static void verifyNotNull(Object obj, String paramName) {
        if (obj == null) {
            throw new IllegalArgumentException("The " + paramName + " must be not null.");
        }
    }
}
